#ifndef _SIMULATOR_SIM_H_
#define _SIMULATOR_SIM_H_

#include "addrdec.h"
#include "types.h"
#include "cache.h"
#include "./ramulator/Ramulator.h"
#include <cstring>

#define MhZ *1000

static int sg_argc = 3;
static const char *sg_argv[] = {"", "-config", "simulator_sim.config"};

class PE;
class PEConfig;

enum DramCtrl
{
  DRAM_FIFO = 0,
  DRAM_FRFCFS = 1
};

class MemoryConfig
{
public:
  MemoryConfig()
  {
    m_valid = false;
  }
  void init()
  {
    assert(m_n_sub_partition_per_memory_channel > 0);
    assert((nbk % m_n_sub_partition_per_memory_channel == 0) &&
           "Number of DRAM banks must be a perfect multiple of memory sub "
           "partition");
    m_n_mem_sub_partition = m_n_mem * m_n_sub_partition_per_memory_channel;
    fprintf(stdout, "Total number of memory sub partition = %u\n",
            m_n_mem_sub_partition);

    m_address_mapping.init(m_n_mem, m_n_sub_partition_per_memory_channel);
    m_l2_config.init(&m_address_mapping);
    m_valid = true;

    sscanf(write_queue_size_opt, "%d:%d:%d",
           &simulator_frfcfs_dram_write_queue_size, &write_high_watermark,
           &write_low_watermark);
  }
  void reg_options(class OptionParser *opp);

  bool m_valid;

  char *simulator_dram_timing_opt;
  char *simulator_L2_queue_config;
  bool l2_ideal;
  uint32_t simulator_frfcfs_dram_sched_queue_size;
  uint32_t simulator_dram_return_queue_size;
  enum DramCtrl scheduler_type;
  bool simulator_memlatency_stat;
  uint32_t m_n_mem;
  uint32_t m_n_sub_partition_per_memory_channel;
  uint32_t m_n_mem_sub_partition;
  uint32_t simulator_n_mem_per_ctrlr;

  uint32_t dram_latency;

  // DRAM parameters

  uint32_t tCCDL; // column to column delay when bank groups are enabled
  uint32_t tRTPL; // read to precharge delay when bank groups are enabled for
                  // GDDR5 this is identical to RTPS, if for other DRAM this is
                  // different, you will need to split them in two

  uint32_t tCCD;   // column to column delay
  uint32_t tRRD;   // minimal time required between activation of rows in
                   // different banks
  uint32_t tRCD;   // row to column delay - time required to activate a row
                   // before a read
  uint32_t tRCDWR; // row to column delay for a write command
  uint32_t tRAS;   // time needed to activate row
  uint32_t tRP;    // row precharge ie. deactivate row
  uint32_t
      tRC;        // row cycle time ie. precharge current, then activate different row
  uint32_t tCDLR; // Last data-in to Read command (switching from write to
                  // read)
  uint32_t tWR;   // Last data-in to Row precharge

  uint32_t CL;   // CAS latency
  uint32_t WL;   // WRITE latency
  uint32_t BL;   // Burst Length in bytes (4 in GDDR3, 8 in GDDR5)
  uint32_t tRTW; // time to switch from read to write
  uint32_t tWTR; // time to switch from write to read
  uint32_t tWTP; // time to switch from write to precharge in the same bank
  uint32_t busW;

  uint32_t nbkgrp; // number of bank groups (has to be power of 2)
  uint32_t
      bk_tag_length; // number of bits that define a bank inside a bank group

  uint32_t nbk;

  bool elimnate_rw_turnaround;

  uint32_t
      data_command_freq_ratio; // frequency ratio between DRAM data bus and
                               // command bus (2 for GDDR3, 4 for GDDR5)
  uint32_t
      dram_atom_size; // number of bytes transferred per read or write command

  linear_to_raw_address_translation m_address_mapping;

  uint32_t icnt_flit_size;

  uint32_t dram_bnk_indexing_policy;
  uint32_t dram_bnkgrp_indexing_policy;
  bool dual_bus_interface;

  bool seperate_write_queue_enabled;
  char *write_queue_size_opt;
  uint32_t simulator_frfcfs_dram_write_queue_size;
  uint32_t write_high_watermark;
  uint32_t write_low_watermark;
  bool m_perf_sim_memcpy;
  bool simple_dram_model;
  mutable L2CacheConfig m_l2_config;
};

class SimulatorConfig
{

public:
  SimulatorConfig();

  void init(option_parser_t opp);
  void reg_options(option_parser_t opp);
  uint32_t mem2device(uint32_t memid) const { return memid+num_pes; }

public:
  void init_clock_domains(void);

  // backward pointer
  bool m_valid;
  PEConfig *m_pe_config;
  MemoryConfig *m_memory_config;

  // clock domains - frequency
  double core_freq;
  double icnt_freq;
  double l2_freq;
  double dram_freq;
  double core_period;
  double icnt_period;
  double l2_period;
  double dram_period;

  // simulator-Sim timing model options
  bool simulator_flush_l1_cache;
  int simulator_frfcfs_dram_sched_queue_size;
  int simulator_cflog_interval;
  char *simulator_clock_domains;
  char *simulator_ramulator_config;
  uint32_t simulator_ramulator_cache_line_size;
  uint32_t num_pes;


  bool m_debug_timing;
};

class MemoryPartitionUnit;
class MemorySubPartition;

class SIMULATOR
{
public:
  PE **m_pes;

  void cycle();
  void init();
  bool active();
  SIMULATOR(SimulatorConfig *);

  uint32_t next_clock_domain();

  SimulatorConfig *m_config;
  PEConfig *m_pe_config;
  MemoryConfig *m_memory_config;
  uint64_t simulator_tot_sim_cycle;
  uint64_t simulator_sim_cycle;


protected:
  MemoryPartitionUnit **m_memory_partition_unit;
  MemorySubPartition **m_memory_sub_partition;

private:
  double core_time;
  double icnt_time;
  double l2_time;
  double dram_time;
  Ramulator m_ramulator_wrapper;
};

#endif